<template>
  <div ref="scrollbarRef" :class="wrapperClass">
    <div ref="wrapRef" :class="wrapKls" :style="style" @scroll="handleScroll">
      <component :is="tag" ref="resizeRef" :class="resizeKls" :style="viewStyle">
        <slot />
      </component>
    </div>
    <div v-if="!native">
      <Bar ref="barRef" :height="sizeHeight" :width="sizeWidth" :always="always" :ratio-x="ratioX" :ratio-y="ratioY" />
    </div>
    <!-- <Bar
      ref="barRef"
      :height="sizeHeight"
      :width="sizeWidth"
      :always="always"
      :ratio-x="ratioX"
      :ratio-y="ratioY"/> -->
  </div>
</template>

<script setup>
import {
  computed,
  nextTick,
  onMounted,
  onUpdated,
  provide,
  reactive,
  ref,
  watch,
  defineEmits
} from 'vue'
import { useEventListener, useResizeObserver } from '@vueuse/core'
import { isString, isObject } from '@vue/shared'
import { GAP } from './util'
import Bar from './bar.vue'

defineOptions({
  name: 'Scrollbar',
})

const props = defineProps({
  /**
   * @description height of scrollbar
   */
  height: {
    type: [String, Number],
    default: '',
  },
  /**
   * @description max height of scrollbar
   */
  maxHeight: {
    type: [String, Number],
    default: '',
  },
  /**
   * @description whether to use the native scrollbar
   */
  native: {
    type: Boolean,
    default: false,
  },
  /**
   * @description style of wrap
   */
  wrapStyle: {
    type: [String, Object, Array],
    default: '',
  },
  /**
   * @description class of wrap
   */
  wrapClass: {
    type: [String, Array],
    default: '',
  },
  /**
   * @description class of view
   */
  viewClass: {
    type: [String, Array],
    default: '',
  },
  /**
   * @description style of view
   */
  viewStyle: {
    type: [String, Array, Object],
    default: '',
  },
  /**
   * @description do not respond to container size changes, if the container size does not change, it is better to set it to optimize performance
   */
  noresize: Boolean, // 如果 container 尺寸不会发生变化，最好设置它可以优化性能
  /**
   * @description element tag of the view
   */
  tag: {
    type: String,
    default: 'div',
  },
  /**
   * @description always show
   */
  always: Boolean,
  /**
   * @description minimum size of scrollbar
   */
  minSize: {
    type: Number,
    default: 20,
  },
})

const emits = defineEmits(['scroll'])

let stopResizeObserver = undefined
let stopResizeListener = undefined

const scrollbarRef = ref()
const wrapRef = ref()
const resizeRef = ref()

const sizeWidth = ref('0')
const sizeHeight = ref('0')
const barRef = ref()
const ratioY = ref(1)
const ratioX = ref(1)

const prefixCls = 'haloe-scrollbar'

const wrapperClass = computed(() => {
  return [prefixCls]
})

const isNumber = (val) => typeof val === 'number'

const isStringNumber = (val) => {
  if (!isString(val)) {
    return false
  }
  return !Number.isNaN(Number(val))
}
function addUnit(value, defaultUnit = 'px') {
  if (!value) return ''
  if (isNumber(value) || isStringNumber(value)) {
    return `${value}${defaultUnit}`
  } else if (isString(value)) {
    return value
  }
  console.debug('binding value must be a string or number')
}

const style = computed(() => {
  const style = {}
  if (props.height) style.height = addUnit(props.height)
  if (props.maxHeight) style.maxHeight = addUnit(props.maxHeight)
  return [props.wrapStyle, style]
})

const wrapKls = computed(() => {
  return [
    props.wrapClass,
    prefixCls + '-wrap',
    { [prefixCls + '-wrap-hidden-default']: !props.native },
  ]
})

const resizeKls = computed(() => {
  return [prefixCls + '-view', props.viewClass]
})

const handleScroll = () => {
  if (wrapRef.value) {

    if (barRef.value) barRef.value.handleScroll(wrapRef.value)

    emits('scroll', {
      scrollTop: wrapRef.value.scrollTop,
      scrollLeft: wrapRef.value.scrollLeft,
    })
  }
}


function scrollTo(arg1, arg2) {
  if (isObject(arg1)) {
    wrapRef.value.scrollTo(arg1)
  } else if (isNumber(arg1) && isNumber(arg2)) {
    wrapRef.value.scrollTo(arg1, arg2)
  }
}

const setScrollTop = (value) => {
  if (!isNumber(value)) {
    console.warn('value must be a number')
    return
  }
  wrapRef.value.scrollTop = value
}

const setScrollLeft = (value) => {
  if (!isNumber(value)) {
    console.warn('value must be a number')
    return
  }
  wrapRef.value.scrollLeft = value
}

const update = () => {
  if (!wrapRef.value) return
  const offsetHeight = wrapRef.value.offsetHeight - GAP
  const offsetWidth = wrapRef.value.offsetWidth - GAP

  const originalHeight = offsetHeight ** 2 / wrapRef.value.scrollHeight
  const originalWidth = offsetWidth ** 2 / wrapRef.value.scrollWidth
  const height = Math.max(originalHeight, props.minSize)
  const width = Math.max(originalWidth, props.minSize)

  ratioY.value =
    originalHeight /
    (offsetHeight - originalHeight) /
    (height / (offsetHeight - height))
  ratioX.value =
    originalWidth /
    (offsetWidth - originalWidth) /
    (width / (offsetWidth - width))

  sizeHeight.value = height + GAP < offsetHeight ? `${height}px` : ''
  sizeWidth.value = width + GAP < offsetWidth ? `${width}px` : ''
}

watch(
  () => props.noresize,
  (noresize) => {
    if (noresize) {
      stopResizeObserver()
      stopResizeListener()
    } else {
      ; ({ stop: stopResizeObserver } = useResizeObserver(resizeRef, update))
      stopResizeListener = useEventListener('resize', update)
    }
  },
  { immediate: true }
)

watch(
  () => [props.maxHeight, props.height],
  () => {
    if (!props.native)
      nextTick(() => {
        update()
        if (wrapRef.value) {
          barRef.value.handleScroll(wrapRef.value)
        }
      })
  }
)

provide(
  'scrollbarContextKey',
  reactive({
    scrollbarElement: scrollbarRef,
    wrapElement: wrapRef,
  })
)

onMounted(() => {
  if (!props.native)
    nextTick(() => {
      update()
    })
  console.log("scrollbar", props.native)
})
onUpdated(() => update())

defineExpose({
  /** @description scrollbar wrap ref */
  wrapRef,
  /** @description update scrollbar state manually */
  update,
  /** @description scrolls to a particular set of coordinates */
  scrollTo,
  /** @description set distance to scroll top */
  setScrollTop,
  /** @description set distance to scroll left */
  setScrollLeft,
  /** @description handle scroll event */
  handleScroll,
})
</script>
